﻿using launcher.Common;
using launcher.Models;
using launcher.Services;
using Microsoft.UI;
using Microsoft.UI.Xaml.Documents;
using Microsoft.UI.Xaml.Media;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Windows.Storage;
using Windows.UI.Text;

namespace launcher.ViewModels
{
    internal class MainPageViewModel : BindableBase
    {
        private readonly AppModel m_appModel;
        private readonly List<string> m_cachedTextList = new();
        private readonly List<string> m_processedTextList = new();
        private readonly List<string> m_displayedTextList = new();
        private bool m_launchEnabled = true;
        private bool m_listeningPaused = false;
        private bool m_hasDisplayedText = false;
        private CancellationTokenSource m_cancellationTokenSource;

        public string ExecutableFilePath
        {
            get => m_appModel.LocalSettings.LastExecutableFile;
            set
            {
                if (!string.Equals(ExecutableFilePath, value))
                {
                    m_appModel.LocalSettings.LastExecutableFile = value;
                    OnExecutableFileReset(value);
                }
            }
        }

        public string RegexFilter
        {
            get => m_appModel.LocalSettings.LastRegexFilter;
            set
            {
                if (!string.Equals(RegexFilter, value))
                {
                    m_appModel.LocalSettings.LastRegexFilter = value;
                    OnRegexFilterReset(value);
                }
            }
        }

        public string TextFilter
        {
            get => m_appModel.LocalSettings.LastTextFilter;
            set
            {
                if (!string.Equals(TextFilter, value))
                {
                    m_appModel.LocalSettings.LastTextFilter = value;
                    OnTextFilterReset(value);
                }
            }
        }

        public bool LaunchEnabled
        {
            get => m_launchEnabled;
            private set => SetProperty(ref m_launchEnabled, value);
        }

        public bool ListeningPaused
        {
            get => m_listeningPaused; 
            set => SetProperty(ref m_listeningPaused, value);
        }

        public bool HasDisplayedText
        {
            get => m_hasDisplayedText;
            set => SetProperty(ref m_hasDisplayedText, value);
        }

        public ObservableCollection<Paragraph> TextParagraphs { get; } = new();

        public IEnumerable<string> HistoryTextFilters 
        { 
            get => m_appModel.LocalSettings.HistroryTextFilters;
        }

        public IEnumerable<string> HistoryRegexFilters 
        { 
            get => m_appModel.LocalSettings.HistoryRegexFilters;
        }

        public IEnumerable<string> HistoryExecutableFiles 
        { 
            get => m_appModel.LocalSettings.HistoryExecutableFiles; 
        }

        public string ProcessName { get; private set; }

        public MainPageViewModel(AppModel appModel)
        {
            m_appModel = appModel;
        }

        public async Task Launch(string filePath)
        {
            ListeningPaused = false;
            LaunchEnabled = false;

            using Process process = new();
            m_cancellationTokenSource = new();

            try
            {
                process.StartInfo.FileName = filePath;
                process.StartInfo.UseShellExecute = false;
                process.StartInfo.RedirectStandardError = true;
                process.Start();
                ProcessName = process.ProcessName;

                _ = ReadTextLinesAsync(process.StandardError, m_cancellationTokenSource.Token);
                await process.WaitForExitAsync(m_cancellationTokenSource.Token);
            }
            catch (Exception ex)
            {
                if (ex is TaskCanceledException)
                {
                    if (m_appModel.LocalSettings.KillProcessWhenStop == true)
                    {
                        process.Kill();
                    }
                    process.StandardError.Close();
                }
                else
                {
                    Debug.Write("Start process failed: " + ex.Message + System.Environment.NewLine);
                }
            }
            finally
            {
                process.Close();
                LaunchEnabled = true;
                m_cancellationTokenSource = null;
            }
        }

        public void PauseResumeListening()
        {
            ListeningPaused = !ListeningPaused;
            
            if (!m_listeningPaused)
            {
                ProcessCachedText();
            }
        }

        public void StopListening()
        {
            m_cancellationTokenSource?.Cancel();

            // 丢弃缓存但未处理的数据，但保留已经处理过的数据
            m_cachedTextList.Clear();
        }

        public void ClearCurrentText()
        {
            m_cachedTextList.Clear();
            m_processedTextList.Clear();
            ClearDisplayedText();
        }

        public void ReloadDisplayedText()
        {
            OnFiltersReset();
        }

        public async Task SaveDisplayedText(StorageFile file)
        {
            using Stream stream = await file.OpenStreamForWriteAsync();
            using StreamWriter writer = new(stream);
            foreach (var line in m_displayedTextList)
            {
                await writer.WriteLineAsync(line);
            }
        }

        private void ClearDisplayedText()
        {
            TextParagraphs.Clear();
            m_displayedTextList.Clear();
            OnDisplayedTextChanged();
        }

        private void OnDisplayedTextChanged()
        {
            HasDisplayedText = (m_displayedTextList.Count != 0);
        }

        private async Task ReadTextLinesAsync(StreamReader reader, CancellationToken token)
        {
            Regex beginPattern = new(Settings.RegexPatterns.LogBegin);
            StringBuilder textBuilder = new();

            while (!token.IsCancellationRequested)
            {
                if (await reader.ReadLineAsync() is not string str)
                {
                    if (reader.EndOfStream)
                    {
                        break;
                    }
                    else
                    {
                        continue;
                    }
                }

                if (token.IsCancellationRequested)
                {
                    break;
                }

                if (beginPattern.IsMatch(str))
                {
                    if (textBuilder.Length > 0)
                    {
                        SendText(textBuilder.ToString());
                        textBuilder.Clear();
                    }
                }
                if (textBuilder.Length != 0)
                {
                    textBuilder.AppendLine();
                }
                textBuilder.Append(str);
            }
        }

        private void OnExecutableFileReset(string fileName)
        {
            if (!string.IsNullOrEmpty(fileName))
            {
                m_appModel.LocalSettings.HistoryExecutableFiles = 
                    StringUtils.AppendItemIfNotContains(m_appModel.LocalSettings.HistoryExecutableFiles, fileName);
            }
        }

        private void OnTextFilterReset(string text)
        {
            if (!string.IsNullOrEmpty(text))
            {
                m_appModel.LocalSettings.HistroryTextFilters = 
                    StringUtils.AppendItemIfNotContains(m_appModel.LocalSettings.HistroryTextFilters, text);
            }
            OnFiltersReset();
        }

        private void OnRegexFilterReset(string regex)
        {
            if (!string.IsNullOrEmpty(regex))
            {
                m_appModel.LocalSettings.HistoryRegexFilters = 
                    StringUtils.AppendItemIfNotContains(m_appModel.LocalSettings.HistoryRegexFilters, regex);
            }
            OnFiltersReset();
        }

        private void OnFiltersReset()
        {
            ClearDisplayedText();

            foreach (string item in m_processedTextList)
            {
                ProcessTextLine(item);
            }
        }

        private void SendText(string text)
        {
            m_cachedTextList.Add(text);

            if (!m_listeningPaused)
            {
                ProcessCachedText();
            }
        }

        private void ProcessCachedText()
        {
            Debug.Assert(!m_listeningPaused);
            while (m_cachedTextList.Count > 0)
            {
                string text = m_cachedTextList[0];
                m_cachedTextList.RemoveAt(0);
                m_processedTextList.Add(text);
                ProcessTextLine(text);
            }
        }

        private void ProcessTextLine(string text)
        {
            if (IsMatchFilters(text))
            {
                TextParagraphs.Add(MakeParagraph(text));
                m_displayedTextList.Add(text);
                OnDisplayedTextChanged();
            }
        }

        private bool IsMatchFilters(string text)
        {
            string textFilter = TextFilter;
            string regexFilter = RegexFilter;

            if (string.IsNullOrEmpty(textFilter) && string.IsNullOrEmpty(regexFilter))
            {
                return true;
            }
            if (!string.IsNullOrEmpty(textFilter) && text.Contains(textFilter, StringComparison.OrdinalIgnoreCase))
            {
                return true;
            }
            if (!string.IsNullOrEmpty(regexFilter))
            {
                Regex regex = new(regexFilter);
                if (regex.IsMatch(text))
                {
                    return true;
                }
            }
            return false;
        }

        private Paragraph MakeParagraph(string text)
        {
            Paragraph paragraph = new();
            string textFilter = TextFilter;

            if (!string.IsNullOrEmpty(textFilter))
            {
                string remain = text;
                while (!string.IsNullOrEmpty(remain))
                {
                    int nextIndex = remain.IndexOf(textFilter, StringComparison.OrdinalIgnoreCase);
                    if (nextIndex == -1)
                    {
                        paragraph.Inlines.Add(new Run { Text = remain });
                        break;
                    }

                    paragraph.Inlines.Add(new Run { Text = remain.Substring(0, nextIndex) });

                    Run run = new() { Text = remain.Substring(nextIndex, textFilter.Length) };
                    //run.Foreground = new SolidColorBrush(Colors.Red);
                    Underline underline = new();
                    underline.Inlines.Add(run);
                    paragraph.Inlines.Add(underline);
                    remain = remain.Substring(nextIndex + textFilter.Length);
                }
            }
            else
            {
                paragraph.Inlines.Add(new Run { Text = text });
            }
            return paragraph;
        }
    }
}
